package com.catena.core;

import com.catena.core.annotation.CatenaNodeClass;
import com.catena.core.annotation.CatenaNodeMethod;
import com.catena.core.annotation.CatenaNodeOperationLink;
import com.catena.core.annotation.CatenaType;
import com.catena.core.chain.NodeOperationNormal;
import com.catena.core.chain.NodeOperationReturn;
import com.catena.core.chain.NodeRoot;
import com.catena.core.node.CatenaNode;
import com.catena.util.ClassFilter;
import com.catena.util.ClassUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingDeque;

/**
 * Created by hx-pc on 16-2-24.
 */
public class CatenaContextImpl implements CatenaContext {

    private static final Logger LOGGER = LoggerFactory.getLogger(CatenaContext.class);
    private static final String INTERFACEFOOTNAME = "interface";
    private static final String CLASSFOOTNAME = "class";
    private static final String SCANPACKAGE = "com.catena";
    private List<Class> classes;
    /**
     * 节点池,保证单例
     */
    private Map<Class, CatenaNode> nodePool = new HashMap<>();
    private Map<String, NodeRoot> nodeOperationMap = new HashMap<>();
    private List<Method> nodeMethodLinkList = new CopyOnWriteArrayList<>();
    private Map<String, BlockingQueue<CatenaNode>> nodeUseNodeList = new HashMap<>();
    private Map<String, Class> nodeUseParamClazz = new HashMap<>();
    private Map<String, BlockingQueue<String>> nodeUseMethodQueue = new HashMap<>();

    /**
     * 扫描后缀未class,并且拥有CatenaNodeClass注解
     */
    private ClassFilter classFilter = clazz -> {
        if (clazz.toString().startsWith(INTERFACEFOOTNAME)) {
            CatenaNodeClass cnc = (CatenaNodeClass) clazz.getAnnotation(CatenaNodeClass.class);
            if (cnc != null) {
                return cnc.type() == CatenaType.INTEL;
            }
        }
        return false;
    };

    /**
     * 在项目启动后,applicationContext加载完成后加载
     * 将自己的上下文加入到applicationContext中去
     *
     * @throws BeansException
     */
    public CatenaContextImpl() {
        classes = ClassUtils.scanPackage(SCANPACKAGE, classFilter);
        LOGGER.info("查询到的class{}", classes);
        setNodeOperationRepository();
        setNodeOperationLink();
        showNodeRoot();
    }

    @Override
    public void showNodeRoot() {
        nodeOperationMap.values().forEach(nodeRoot -> LOGGER.info("Classing Catena \"{}\"", nodeRoot.showNode()));
    }

    /**
     * 把拥有CatenaNodeClass注解的类扫描进来
     * 节点生成的一条执行器会放到CatenaContext里面,并且用key来标示,执行器也是单例的
     * 每个节点都有一个单例对象,并且会注入到spring
     */
    protected void setNodeOperationRepository() {
        for (Class clazz : classes) {
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                //存放关联的方法,到最后链条都组装好了再进行拼接
                if (method.getAnnotation(CatenaNodeOperationLink.class) != null) {
                    nodeMethodLinkList.add(method);
                }
                try {
                    setNodeWithNodeOperation(method);
                } catch (IllegalAccessException | InstantiationException | NoSuchMethodException e) {
                    throw new NotFoundMehtodOfClass("无法访问该方法", e);
                }
            }
        }
    }

    /**
     * 组装link,链接
     */
    protected void setNodeOperationLink() {
        List<NodeRoot> list = new CopyOnWriteArrayList<>();
        nodeMethodLinkList.forEach(method -> {
            CatenaNodeOperationLink link = method.getAnnotation(CatenaNodeOperationLink.class);
            BlockingQueue<CatenaNode> cns = new LinkedBlockingDeque<>();
            for (String key : link.link()) {
                if (nodeUseNodeList.get(key) != null) {
                    cns.addAll(nodeUseNodeList.get(key));
                }
                list.add(initLinkRoot(nodeUseNodeList.get(key), nodeUseParamClazz.get(key), nodeUseMethodQueue.get(key)));
            }
            nodeUseNodeList.put(link.key(), cns);
            NodeRoot root = list.get(0);
            list.remove(0);
            list.stream().forEach(root::append);
            root.append(new NodeOperationReturn());
            root.setKeyMessage(link.key()).setLinkMessage(Arrays.toString(link.link()));
            //检查key是否重复
            checkNodeOperationKey(link.key());
            nodeOperationMap.put(link.key(), root);
        });
    }


    /**
     * 将线性执行器的加入map
     * <p>
     * 首先拿到节点方法下面的注解CatenNodeMethod
     * 按照类和方法的数量为准,有多少个Class 就有多少个节点,类少于方法的时候用最后一个类代替
     * 执行器的线条数按照方法数组的长度,也就是有多少个数组就有多少条执行器
     * 如果传入的方法用逗号隔开后数量不够节点数量,不够的用最后一个方法来顶替
     * 并且将参数加入，可以在对应的类里面用getParam来获取,如果参数和类对应不上，不够的都为空,一条链只能拥有一个参数
     *
     * @param method 传入的方法
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws NoSuchMethodException
     */
    private void setNodeWithNodeOperation(Method method) throws IllegalAccessException, InstantiationException, NoSuchMethodException {
        CatenaNodeMethod cnm = method.getAnnotation(CatenaNodeMethod.class);
        if (cnm == null) {
            return;
        }
        BlockingQueue<CatenaNode> nodes = new LinkedBlockingDeque<>();
        Class parameter = cnm.paramClass();
        if (cnm.nodes() != null) {
            String[] key = cnm.key();
            String[] methods = cnm.methods();
            Class[] nodeClass = cnm.nodes();
            checkNodeMethodFormat(key, methods);
            for (int i = 0; i < methods.length; i++) {
                String[] methodNames = methods[i].split(",");
                BlockingDeque<String> listMethod = new LinkedBlockingDeque<>();
                Collections.addAll(listMethod, methodNames);
                if (key[i] != null) {
                    recordUseParam(key[i], nodes, parameter, listMethod);
                    checkNodeOperationKey(key[i]);
                    //判断方法和节点长度,不同的用最后一个节点来补充
                    if (cnm.nodes().length < methodNames.length) {
                        nodeClass = new Class[methodNames.length];
                        for (int j = 0; j < nodeClass.length; j++) {
                            if (cnm.nodes().length == 0) {
                                throw new IllegalAccessException("节点参数不能为空");
                            }
                            if (j >= cnm.nodes().length) {
                                nodeClass[j] = cnm.nodes()[cnm.nodes().length - 1];
                            } else {
                                nodeClass[j] = cnm.nodes()[j];
                            }
                        }
                    }
                    initNodes(nodes, nodeClass);
                    nodeOperationMap.put(key[i], initRoot(key[i], nodes, parameter, listMethod).setKeyMessage(key[i]));
                }
            }
        }
    }

    private void initNodes(BlockingQueue<CatenaNode> nodes, Class[] nodeClass) throws InstantiationException, IllegalAccessException {
        for (Class aClass : nodeClass) {
            nodes.add(checkClassIsInPool(aClass));
        }
    }

    private void recordUseParam(String key, BlockingQueue<CatenaNode> nodes, Class paramClazz, BlockingQueue<String> methodQueue) {
        nodeUseNodeList.put(key, nodes);
        nodeUseParamClazz.put(key, paramClazz);
        nodeUseMethodQueue.put(key, methodQueue);
    }

    /**
     * 检查class是否在本地池里面,在就加入,不在则加入
     *
     * @param clazz
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private CatenaNode checkClassIsInPool(Class clazz) throws IllegalAccessException, InstantiationException {
        if (!nodePool.containsKey(clazz)) {
            this.nodePool.put(clazz, (CatenaNode) clazz.newInstance());
        }
        return this.nodePool.get(clazz);
    }


    /**
     * 检查格式，各个参数的长度
     *
     * @param key
     * @param methods
     */
    private void checkNodeMethodFormat(String[] key, String[] methods) {
        if (key.length != methods.length) {
            LOGGER.debug("key数量只能等于方法数组长度");
            throw new KeyLengthNotSameWithMehtodNamesLength();
        }
    }

    /**
     * 构造一个执行器
     *
     * @param nodes
     * @return
     */
    private NodeRoot initRoot(String key, BlockingQueue<CatenaNode> nodes, Class paramClazz, BlockingQueue<String> methodNameQueue) {
        NodeRoot root = new NodeRoot();
        root.setParamClass(paramClazz);
        BlockingQueue<Method> methodQueue = new LinkedBlockingDeque<>();
        nodes.forEach(node -> {
            try {
                methodQueue.add(node.getClass().getMethod(methodNameQueue.peek(), paramClazz));
            } catch (NoSuchMethodException e) {
                LOGGER.warn("方法不匹配 {}", e.getMessage());
                try {
                    methodQueue.add(node.getClass().getMethod(methodNameQueue.peek(), NodeParameter.class));
                } catch (NoSuchMethodException e1) {
                    LOGGER.warn("方法不匹配 {}", e1.getMessage());
                    try {
                        methodQueue.add(node.getClass().getMethod(methodNameQueue.peek()));
                    } catch (NoSuchMethodException e2) {
                        LOGGER.error("参数配置警告,链条{} : 错误参数 {}, 错误{}", key, e2.getMessage(), e2);
                        throw new NoSuchMethodWithParam();
                    }
                }
            }
            if (methodNameQueue.size() != 1) {
                methodNameQueue.offer(methodNameQueue.poll());
            }
        });
        root.setMethodQueue(methodQueue);
        root.setNodeQueue(nodes);
        nodes.forEach(node -> root.append(new NodeOperationNormal()));
        root.append(new NodeOperationReturn());
        return root;
    }

    /**
     * 构造一个执行器
     */
    private NodeRoot initLinkRoot(BlockingQueue<CatenaNode> nodes, Class paramClazz, BlockingQueue<String> methodNameQueue) {
        NodeRoot root = new NodeRoot();
        root.setParamClass(paramClazz);
        BlockingQueue<Method> methodQueue = new LinkedBlockingDeque<>();
        nodes.forEach(node -> {
            try {
                methodQueue.add(node.getClass().getMethod(methodNameQueue.peek(), paramClazz));
            } catch (NoSuchMethodException e) {
                LOGGER.warn("方法不匹配 {}", e.getMessage());
                try {
                    methodQueue.add(node.getClass().getMethod(methodNameQueue.peek(), NodeParameter.class));
                } catch (NoSuchMethodException e1) {
                    LOGGER.warn("方法不匹配 {}", e1.getMessage());
                    try {
                        methodQueue.add(node.getClass().getMethod(methodNameQueue.peek()));
                    } catch (NoSuchMethodException e2) {
                        LOGGER.error("参数配置警告 : 错误参数 {}, 错误{}", e1.getMessage(), e2);
                        throw new NoSuchMethodWithParam();
                    }
                }
            }
            if (methodNameQueue.size() != 1) {
                methodNameQueue.offer(methodNameQueue.poll());
            }
        });
        root.setMethodQueue(methodQueue);
        root.setNodeQueue(nodes);
        nodes.forEach(node -> root.append(new NodeOperationNormal()));
        return root;
    }

    private void checkNodeOperationKey(String key) {
        if (nodeOperationMap.containsKey(key)) {
            LOGGER.debug("key有重复，请检查");
            throw new KeyMapIsAbsent(" key有重复，请检查: " + key);
        }
    }

    @Override
    public Map<String, NodeRoot> getNodeOperationRepository() {
        return nodeOperationMap;
    }

    @Override
    public Map<Class, CatenaNode> getNodePool() {
        return nodePool;
    }

}
